/*
 * create by yuri april 2 2011
 */
package implementation.server;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.Scanner;
import java.util.logging.Logger;

import implementation.shared.Config;
import implementation.shared.EPacketParam;
import implementation.shared.EPacketType;
import implementation.shared.INotification;
import implementation.shared.IPacket;
import implementation.shared.SharedFactory;
import implementation.shared.stubs.IForumStub;
import implementation.shared.stubs.IPostStub;
import implementation.shared.stubs.IThreadStub;
import implementation.shared.stubs.IUserStub;

public class Server implements IServer {

    private static final String MESSAGE_NO_PERMISSION = "No Permission.";
    private static final String MESSAGE_FRIEND_ADDED_SUCCESSFULLY = "Friend added successfully";
    private static final String MESSAGE_DATABASE_ERROR = "Database error.";
    private static final String MESSAGE_REGISTRATION_SUCCESSFUL = "User registered successfully.";
    private static final String MESSAGE_SHORT_USERNAME = "Username is too short";
    private static final String MESSAGE_SHORT_PASSWORD = "Password is too short";
    private static final String MESSAGE_USERNAME_EXISTS = "Selected username already exists.";
    private static final String MESSAGE_LOGIN_SUCCESSFUL = "Logged in succesfully.";
    private static final String MESSAGE_INVALID_USERNAME_OR_PASSWORD = "Username or Password are invalid.";
    private static final int MIN_USERNAME_LENGTH = 3;
    private static final int MIN_PASSWORD_LENGTH = 6;
    private String _lastError = "";
    private static final Logger _logger = Logger.getLogger(Server.class.toString());

    @Override
    public IPacket validate(String username, String password) {
        _logger.info("Validating: " + username + "/" + password);
        int userID = _db.getUserID(username);
        if (userID <= 0) {
            return ServerFactory.createFailPacket(MESSAGE_INVALID_USERNAME_OR_PASSWORD);
        }
        IReadOnlyUser user = _db.getUser(userID);
        if (!user.get_password().equals(password)) {
            return ServerFactory.createFailPacket(MESSAGE_INVALID_USERNAME_OR_PASSWORD);
        }
        return ServerFactory.createSuccessPacket(MESSAGE_LOGIN_SUCCESSFUL);
    }

    @Override
    public IPacket register(String username, String password) {
        _logger.info("Registering: " + username + "/" + password);
        int userID = _db.getUserID(username);
        if (userID >= 0) {
            return ServerFactory.createFailPacket(MESSAGE_USERNAME_EXISTS);
        }
        if (password.length() < MIN_PASSWORD_LENGTH) {
            return ServerFactory.createFailPacket(MESSAGE_SHORT_PASSWORD);
        }
        if (username.length() < MIN_USERNAME_LENGTH) {
            return ServerFactory.createFailPacket(MESSAGE_SHORT_USERNAME);
        }
        _db.addUser(username, password);
        return ServerFactory.createSuccessPacket(MESSAGE_REGISTRATION_SUCCESSFUL);
    }

    @Override
    public int getUserID(String username) {
        return _db.getUserID(username);
    }
    private static IDB _db;
    private static IServerConnectionManager _connectionManager;
    private static ISessionManager _sessionManager;

    public static void main(String[] args) {
        IServer system = new Server();
        _db = ServerFactory.getBDSingletone();
        _sessionManager = ServerFactory.getSessionManagerSingletone(system);
        _connectionManager = ServerFactory.getServerConnectionManagerSingletone(_sessionManager);
        if (!_connectionManager.start(Config.PORT)) {
            System.out.println("Could not start server. Bye.");
            return;
        }
        System.out.println("Server is running.");
        waitForShutdown();
        System.out.println("Server shutting down...");
        _sessionManager.closeAll();
        _connectionManager.close();
        System.out.println("Done. Bye.");
    }

    private static void waitForShutdown() {
        Scanner s = new Scanner(System.in);
        boolean shutdown = false;
        while (!shutdown) {
            System.out.println("Waiting for input:");
            System.out.println("0 - shutdown");
            System.out.println("1 - stats");
            int input = s.nextInt();
            switch (input) {
                case 0: {
                    shutdown = true;
                    break;
                }
                case 1: {
                    System.out.println("Sorry, not yet implemented.");
                    break;
                }
                default: {
                    break;
                }
            }
        }

    }

    @Override
    public void logout(int userID) {
        _sessionManager.closeSession(userID);
    }

    @Override
    public IPacket getAllForums() {
        IPacket forumsPacket = SharedFactory.createPacket(EPacketType.EGetAllForums);
        ArrayList<IReadOnlyForum> forums = _db.getForums();
        ArrayList<IForumStub> forumStubs = new ArrayList<IForumStub>();
        for (Iterator<IReadOnlyForum> iterator = forums.iterator(); iterator.hasNext();) {
            IReadOnlyForum iReadOnlyForum = (IReadOnlyForum) iterator.next();
            forumStubs.add(ServerFactory.createForumStub(iReadOnlyForum.getName(), iReadOnlyForum.getID(), iReadOnlyForum.getThreads().size()));

        }
        forumsPacket.addParam(EPacketParam.ECollection, forumStubs);
        return forumsPacket;
    }

    @Override
    public IPacket addFriend(int userID, String friendName) {
        _logger.info("Adding friend: " + friendName + " to user:" + userID);
        int friendID = _db.getUserID(friendName);
        if (friendID <= 0) {
            return ServerFactory.createFailPacket(MESSAGE_DATABASE_ERROR);
        }
        if (!_db.follow(userID, friendID)) {
            return ServerFactory.createFailPacket(MESSAGE_DATABASE_ERROR);
        }
        return ServerFactory.createSuccessPacket(MESSAGE_FRIEND_ADDED_SUCCESSFULLY);
    }

    @Override
    public String getLastError() {
        return this._lastError;
    }

    @Override
    public boolean addForum(int _userID, String forumName) {
        boolean success = (_db.addForum(forumName) >= 0);
        if (!success) {
            this._lastError = MESSAGE_DATABASE_ERROR;
        }
        return success;
    }

    @Override
    public Collection<IThreadStub> getAllThreads(int forumID) {
        ArrayList<IThreadStub> threadStubs = new ArrayList<IThreadStub>();
        IReadOnlyForum forum = _db.getForum(forumID);
        if (forum == null) {
            this._lastError = MESSAGE_DATABASE_ERROR;
            return null;
        }
        for (Integer threadID : forum.getThreads()) {
            IReadOnlyThread thread = _db.getThread(threadID);
            if (thread == null) {
                this._lastError = MESSAGE_DATABASE_ERROR;
                return null;
            }
            threadStubs.add(ServerFactory.createThreadStub(thread.getID(), thread.getTopic(), thread.getAuthorID(), thread.getPosts().size()));
        }
        return threadStubs;
    }

    @Override
    public Collection<IPostStub> getAllPosts(int threadID) {
        ArrayList<IPostStub> threadStubs = new ArrayList<IPostStub>();
        IReadOnlyThread thread = _db.getThread(threadID);
        if (thread == null) {
            this._lastError = MESSAGE_DATABASE_ERROR;
            return null;
        }
        for (Integer postID : thread.getPosts()) {
            IReadOnlyPost post = _db.getPost(postID);
            if (post == null) {
                this._lastError = MESSAGE_DATABASE_ERROR;
                return null;
            }
            threadStubs.add(ServerFactory.createPostStub(post.getID(), post.getTopic(), post.getContent(), post.getAuthorID()));
        }
        return threadStubs;
    }

    @Override
    public IThreadStub getThread(int threadID) {
        IReadOnlyThread thread = _db.getThread(threadID);
        if (thread == null) {
            this._lastError = MESSAGE_DATABASE_ERROR;
            return null;
        }
        return ServerFactory.createThreadStub(thread.getID(), thread.getTopic(), thread.getAuthorID(), thread.getPosts().size());
    }

    @Override
    public IPostStub getPost(int postID) {
        IReadOnlyPost post = _db.getPost(postID);
        if (post == null) {
            this._lastError = MESSAGE_DATABASE_ERROR;
            return null;
        }
        return ServerFactory.createPostStub(post.getID(), post.getTopic(), post.getContent(), post.getAuthorID());
    }

    @Override
    public Collection<IUserStub> getFriends(int userID) {
    	
        ArrayList<IUserStub> userStubs = new ArrayList<IUserStub>();
        IReadOnlyUser user = _db.getUser(userID);
        if (user == null) {
            this._lastError = MESSAGE_DATABASE_ERROR;
            return null;
        }
        for (Integer friendID : user.getFollowees()) {
            IReadOnlyUser friend = _db.getUser(friendID);
            if (friend == null) {
                this._lastError = MESSAGE_DATABASE_ERROR;
                return null;
            }
            userStubs.add(ServerFactory.createUserStub(friend.get_userName(), friend.get_userId()));
        }
        return userStubs;
    }

    @Override
    public boolean addPost(int userID, int threadID, String topic, String content) {
        _logger.info("User " + userID + " adding post to thread: " + threadID);
        int postID = _db.addPost(threadID, topic, content, userID);
        if (postID > 0) {
            this.sendNotifications(userID, postID);
            return true;
        }
        this._lastError = MESSAGE_DATABASE_ERROR;
        return false;
    }

    private void sendNotifications(int userID, int postID) {
    	_logger.info("Sending notification about user " + userID + " and post " + postID);
        IReadOnlyPost post = _db.getPost(postID);
        IReadOnlyThread thread = _db.getThread(post.getThreadID());
        IReadOnlyForum forum = _db.getForum(post.getForumID());

        IReadOnlyUser user = _db.getUser(userID);

        final INotification n = SharedFactory.createNotification(
                user.get_userName(),
                thread.getTopic(),
                forum.getName(),
                userID,
                post.getThreadID(),
                post.getForumID());
        
        Thread notificationSender = new Thread(new Runnable() {
			
			@Override
			public void run() {
				_sessionManager.sendNotification(n);
			}
		});
        notificationSender.start();

    }

    @Override
    public boolean addThread(int userID, int forumID, String topic, String content) {
        _logger.info("Adding thread to forum: " + forumID);
        if (_db.addThread(forumID, topic, content, userID) > 0) {
            return true;
        }
        this._lastError = MESSAGE_DATABASE_ERROR;
        return false;
    }

    @Override
    public boolean editPost(int editorID, int postID, String topic, String content) {
        IReadOnlyPost post = _db.getPost(postID);
        if (post == null) {
            this._lastError = MESSAGE_DATABASE_ERROR;
            return false;
        }
        if (!hasPermission(editorID, EPacketType.EEditPost, post)) {
            this._lastError = MESSAGE_NO_PERMISSION;
            return false;
        }
        if (!_db.editPost(postID, topic, content)) {
            this._lastError = MESSAGE_DATABASE_ERROR;
            return false;
        }
        return true;
    }

    @Override
    public boolean removePost(int userID, int postID) {
        IReadOnlyPost post = _db.getPost(postID);
        if (post == null) {
            this._lastError = MESSAGE_DATABASE_ERROR;
            return false;
        }
        if (!hasPermission(userID, EPacketType.ERemovePost, post)) {
            this._lastError = MESSAGE_NO_PERMISSION;
            return false;
        }
        if (!_db.removePost(postID)) {
            this._lastError = MESSAGE_DATABASE_ERROR;
            return false;
        }
        return true;
    }

    @Override
    public boolean removeThread(int userID, int threadID) {
        IReadOnlyThread thread = _db.getThread(threadID);
        if (thread == null) {
            this._lastError = MESSAGE_DATABASE_ERROR;
            return false;
        }
        if (!hasPermission(userID, EPacketType.ERemoveThread, thread)) {
            this._lastError = MESSAGE_NO_PERMISSION;
            return false;
        }
        if (!_db.removePost(threadID)) {
            this._lastError = MESSAGE_DATABASE_ERROR;
            return false;
        }
        return true;
    }

    @Override
    public boolean removeFriend(int userID, int friendID) {
        _logger.info("Removing friend: " + friendID + " from user:" + userID);
        if (!_db.stopFollowing(userID, friendID)) {
            this._lastError = MESSAGE_DATABASE_ERROR;
            return false;
        }
        return true;
    }

    private boolean hasPermission(int userID, EPacketType operation, Object target) {
        switch (operation) {

            case ERemovePost:
            case EEditPost: {
                if (userID == ((IReadOnlyPost) target).getAuthorID()) {
                    return true;
                }
                return false;
            }
            case ERemoveThread: {
                if (userID == ((IReadOnlyThread) target).getAuthorID()) {
                    return true;
                }
                return false;
            }

        }
        return false;
    }
}
